package org.xiaoxin.onecoupon.distribution.service.handler.excel;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.unit.DataSizeUtil;
import cn.hutool.core.io.unit.DataUnit;
import cn.hutool.core.lang.Singleton;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.BatchExecutorException;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.xiaoxin.onecoupon.distribution.common.constant.DistributionRedisConstant;
import org.xiaoxin.onecoupon.distribution.common.constant.EngineRedisConstant;
import org.xiaoxin.onecoupon.distribution.common.enums.CouponSourceEnum;
import org.xiaoxin.onecoupon.distribution.common.enums.CouponStatusEnum;
import org.xiaoxin.onecoupon.distribution.common.enums.CouponTaskStatusEnum;
import org.xiaoxin.onecoupon.distribution.dao.entity.CouponTaskDO;
import org.xiaoxin.onecoupon.distribution.dao.entity.CouponTaskFailDO;
import org.xiaoxin.onecoupon.distribution.dao.entity.CouponTemplateDO;
import org.xiaoxin.onecoupon.distribution.dao.entity.UserCouponDO;
import org.xiaoxin.onecoupon.distribution.dao.mapper.CouponTaskFailMapper;
import org.xiaoxin.onecoupon.distribution.dao.mapper.CouponTaskMapper;
import org.xiaoxin.onecoupon.distribution.dao.mapper.CouponTemplateMapper;
import org.xiaoxin.onecoupon.distribution.dao.mapper.UserCouponMapper;
import org.xiaoxin.onecoupon.distribution.mq.consumer.CouponExecuteDistributionConsumer;
import org.xiaoxin.onecoupon.distribution.mq.event.CouponTemplateDistributionEvent;
import org.xiaoxin.onecoupon.distribution.mq.producer.CouponExecuteDistributionProducer;
import org.xiaoxin.onecoupon.distribution.toolkit.StockDecrementReturnCombinedUtil;
import com.alibaba.fastjson.JSON;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 优惠券任务读取 Excel 分发监听器
 * @author xiaoxin
 * 2025/4/25
 */
@RequiredArgsConstructor
@Slf4j
public class ReadExcelDistributionListener  extends AnalysisEventListener<CouponTaskExcelObject> {
    private final CouponTaskDO couponTaskDO;
    private final CouponTemplateDO couponTemplateDO;
    private final StringRedisTemplate stringRedisTemplate;

    private  final CouponTaskFailMapper couponTaskFailMapper;

    private  final CouponExecuteDistributionProducer couponExecuteDistributionProducer;
    /**
     * 当前excel处理到第几行
     */
    private int rowCount=1;
    //lua脚本
    private final static String STOCK_DECREMENT_AND_BATCH_SAVE_USER_RECORD_LUA_PATH = "lua/stock_decrement_and_batch_save_user_record.lua";
    private final static int BATCH_USER_COUPON_SIZE = 5000;
    @Override
    public void invoke(CouponTaskExcelObject data, AnalysisContext context) {

        Long couponTaskId = couponTaskDO.getId();
        //获取当前执行进度，判断该行是否执行过，如果执行过则跳过。防止执行到一半应用宕机
        String templateTaskExecuteProgressKey = String.format(DistributionRedisConstant.TEMPLATE_TASK_EXECUTE_PROGRESS_KEY, couponTaskId);
        String progress = stringRedisTemplate.opsForValue().get(templateTaskExecuteProgressKey);
        if (StrUtil.isNotBlank(progress)&&Integer.parseInt(progress)>=rowCount){
            ++rowCount;
            return;
        }
        //获取lua脚本，并保存到Hutool的单例管理器，下次获取不需要加载
        DefaultRedisScript<Long> buildLuaScript = Singleton.get(STOCK_DECREMENT_AND_BATCH_SAVE_USER_RECORD_LUA_PATH, () -> {
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
            redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource(STOCK_DECREMENT_AND_BATCH_SAVE_USER_RECORD_LUA_PATH)));
            redisScript.setResultType(Long.class);
            return redisScript;
        });
        //执行lua脚本进行扣减redis中库存以及增加redis用户领取优惠劵记录
        String couponTemplateKey = String.format(EngineRedisConstant.COUPON_TEMPLATE_KEY, couponTemplateDO.getId());
        String batchUserSetKey = String.format(DistributionRedisConstant.TEMPLATE_TASK_EXECUTE_BATCH_USER_KEY, couponTaskId);
        Map<Object, Object> userRowNumMap = MapUtil.builder()
                .put("userId", data.getUserId())
                .put("rowNum", rowCount + 1)
                .build();
        //这里执行脚本会返回两个结果的拼接值，一个是是否还有库存的标志，一个是用户领券 Set 的长度
        Long combineFiled=stringRedisTemplate.execute(buildLuaScript, List.of(couponTemplateKey,batchUserSetKey),JSON.toJSONString(userRowNumMap));
        //这里专门写了类进行处理，没有使用java中的spilt方法，该方法更快，在5000个数据下快20倍
        boolean firstField = StockDecrementReturnCombinedUtil.extractFirstField(combineFiled);
        //优惠劵没有库存
        if (!firstField){
            //同步当前进度到缓存
            stringRedisTemplate.opsForValue().set(templateTaskExecuteProgressKey,String.valueOf(rowCount));
            ++rowCount;
            // 添加到 t_coupon_task_fail 并标记错误原因，方便后续查看未成功发送的原因和记录
            Map<Object, Object> objectMap = MapUtil.builder()
                    .put("rowNum", rowCount + 1)
                    .put("cause", "优惠劵模板无缓存")
                    .build();
            CouponTaskFailDO couponTaskFailDO = CouponTaskFailDO.builder()
                    .batchId(couponTaskDO.getBatchId())
                    .jsonObject(JSON.toJSONString(objectMap, SerializerFeature.WriteMapNullValue))
                    .build();
            couponTaskFailMapper.insert(couponTaskFailDO);
            return;
        }
        //获取用户领优惠劵集合长度
        int batchUserSetSize = StockDecrementReturnCombinedUtil.extractSecondField(combineFiled.intValue());
        // batchUserSetSize = BATCH_USER_COUPON_SIZE 时发送消息消费，不满足条件仅记录执行进度即可
        if (batchUserSetSize<BATCH_USER_COUPON_SIZE){
            //同步当前进度到缓存
            stringRedisTemplate.opsForValue().set(templateTaskExecuteProgressKey,String.valueOf(rowCount));
            ++rowCount;
            return;
        }
        //若超5000了交给另一个消费者逻辑进行消费
        CouponTemplateDistributionEvent couponTemplateDistributionEvent = CouponTemplateDistributionEvent.builder()
                .couponTemplateId(couponTemplateDO.getId())
                .couponTaskBatchId(couponTaskDO.getBatchId())
                .shopNumber(couponTaskDO.getShopNumber())
                .distributionEndFlag(Boolean.FALSE)
                .couponTemplateConsumeRule(couponTemplateDO.getConsumeRule())
                .batchUserSetSize(batchUserSetSize)
                .couponTaskId(couponTaskId)
                .build();
        couponExecuteDistributionProducer.sendMessage(couponTemplateDistributionEvent);
        //同步当前进度到缓存
        stringRedisTemplate.opsForValue().set(templateTaskExecuteProgressKey,String.valueOf(rowCount));
        ++rowCount;


    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        // 发送 Excel 解析完成标识，即使不满足批量保存的数量也得保存到数据库
        CouponTemplateDistributionEvent couponTemplateExecuteEvent = CouponTemplateDistributionEvent.builder()
                .distributionEndFlag(Boolean.TRUE) // 设置解析完成标识
                .shopNumber(couponTaskDO.getShopNumber())
                .couponTemplateId(couponTemplateDO.getId())
                .couponTemplateConsumeRule(couponTemplateDO.getConsumeRule())
                .couponTaskBatchId(couponTaskDO.getBatchId())
                .couponTaskId(couponTaskDO.getId())
                .build();
        couponExecuteDistributionProducer.sendMessage(couponTemplateExecuteEvent);
    }
}
